

class Node:  # base / text
    def __init__(self, *args, **kwargs):
        self.tag = str(type(self))[14:-2]  # use class name as tag
        self.child_nodes = []
        self.content = str()  # src str without tags such as ' val ' in '{% val %}'
        if kwargs.get('src'):
            self.src_str = kwargs['src']
        else:
            self.src_str = str()

    def get_condition(self, content, **kwargs):
        if not len(content):
            raise RuntimeError('ERROR_CONTENT_EMPTY')
        # build context
        if kwargs.get('context'):
            context = kwargs['context']
        else:
            context = {}
        if kwargs.get('tag'):
            tag = kwargs['tag']
        else:
            tag = ''
        i = 0
        while i < len(content):
            # find beg
            offset = len(tag)
            if content[i:i + offset] == tag:
                i += offset
                return self.eval_condition(content[i:], context)
            i += 1
        return None

    def eval_condition(self, content, context):
        for key in context.keys():
            locals()[key] = context[key]
        i = 0
        while i < len(content):
            if content[i] != ' ':
                condition = content[i:]  # ignore comment
                return eval(compile(condition, '', 'eval'))
            i += 1
        return None

    # render body for wrapper node
    def render_body(self, nodes, **kwargs):
        if kwargs.get('context'):
            context = kwargs['context']
        else:
            context = {}
        text = ''
        for node in nodes:
            text += node.render(context=context)
        return text

    # render node, need to be override
    def render(self, **kwargs):
        return self.src_str


class RootNode(Node):

    def render(self, **kwargs):
        if kwargs.get('context'):
            context = kwargs['context']
        else:
            context = {}
        return self.render_body(self.child_nodes, context=context)


class TextNode(Node):
    pass


class CommentNode(Node):

    def render(self, **kwargs):
        if len(self.src_str) < 4:
            return
        self.content = self.src_str[2:-2]
        return ''


class EndNode(Node):
    def render(self):
        return ''


class ValNode(Node):
    def render(self, **kwargs):
        if kwargs.get('context'):
            context = kwargs['context']
        else:
            context = {}
        content = self.src_str[2:-2]
        i = 0
        # trim whitespaces at begin
        while i < len(content):
            if content[i] != ' ':
                content = content[i:]
                break
            i += 1
        # build context
        for key in context.keys():
            locals()[key] = context[key]
        code = compile(content, '', "eval")
        text = str(eval(code))
        return text


class IfNode(Node):

    def get_condition(self, content, **kwargs):
        kwargs['tag'] = 'if'
        return super(IfNode, self).get_condition(content, **kwargs)

    def get_else_tags(self):
        i = 0
        else_tags = []
        while i < len(self.child_nodes):
            node = self.child_nodes[i]
            if node.tag == 'ElseNode':
                else_tags.append(i)
            elif node.tag == 'ElifNode':
                else_tags.append(i)
            i += 1
        return else_tags

    def render(self, **kwargs):
        # build node
        if kwargs.get('context'):
            context = kwargs['context']
        else:
            context = {}
        self.content = self.src_str[2:-2]
        else_tags = self.get_else_tags()
        # render if
        condition = self.get_condition(self.content, context=context)
        if condition:
            if len(else_tags):
                return self.render_body(self.child_nodes[:else_tags[0]], context=context)
            else:
                return self.render_body(self.child_nodes, context=context)
        # render elif
        i = 0
        while i < len(else_tags) - 1:
            beg = else_tags[i]
            end = else_tags[i + 1]
            else_tag = self.child_nodes[beg]
            content = else_tag.src_str[2:-2]
            condition = self.get_condition(content, context=context)
            if condition:
                text = self.render_body(self.child_nodes[beg + 1:end], context=context)
                return text
            i += 1
        # render last else
        end = else_tags[-1]
        else_tag = self.child_nodes[end]
        if else_tag.tag == 'ElseNode':
            text = self.render_body(self.child_nodes[end + 1:], context=context)
            return text
        elif else_tag.tag == 'ElifNode':
            content = else_tag.src_str[2:-2]
            condition = self.get_condition(content, context=context)
            if condition:
                text = self.render_body(self.child_nodes[end + 1:], context=context)
                return text
            else:
                return ''
        return None


class ElseNode(Node):
    def render(self, **kwargs):
        return ''


class ElifNode(Node):
    def render(self):
        return ''


class IfEqualNode(Node):

    def get_condition(self, content, **kwargs):
        kwargs['tag'] = 'ifequal'
        return super(IfEqualNode, self).get_condition(content, **kwargs)

    def eval_condition(self, content, context):
        for key in context.keys():
            locals()[key] = context[key]
        vals = content.split()
        if len(vals) != 2:
            raise RuntimeError('ERROR_ILLEGAL_EXPRESSION')
        val_a = eval(compile(vals[0], '', 'eval'))
        val_b = eval(compile(vals[1], '', 'eval'))
        return val_a == val_b

    def get_else_tag(self):
        i = 0
        while i < len(self.child_nodes):
            if self.child_nodes[i].tag == 'ElseNode':
                return i
            i += 1
        return -1

    def render(self, **kwargs):
        # build node
        if kwargs.get('context'):
            context = kwargs['context']
        else:
            context = {}
        self.content = self.src_str[2:-2]
        else_tag = self.get_else_tag()
        condition = self.get_condition(self.content, context=context)
        if condition:
            text = self.render_body(self.child_nodes[:else_tag], context=context)
        else:
            text = self.render_body(self.child_nodes[else_tag+1:], context=context)
        return text


class IfNotEqualNode(IfEqualNode):
    def get_condition(self, content, **kwargs):
        return not super(IfNotEqualNode, self).get_condition(content, **kwargs)


class ForNode(Node):
    def get_condition(self, content, **kwargs):
        kwargs['tag'] = 'for'
        return super(ForNode, self).get_condition(content, **kwargs)

    def eval_condition(self, content, context):
        for key in context.keys():
            locals()[key] = context[key]
        _i = 0
        while _i < len(content):
            if content[_i:_i+2] == 'in':
                iter = str().join(content[:_i].split())
                _i += 2
                while _i < len(content):
                    if content[_i] != ' ':
                        arr = eval(compile(content[_i:], '', 'eval'))
                        return iter, arr
                    _i += 1
            _i += 1

    def render(self, **kwargs):
        # build node
        if kwargs.get('context'):
            context = kwargs['context']
        else:
            context = {}
        self.content = self.src_str[2:-2]
        for key in context.keys():
            locals()[key] = context[key]
        iter, arr = self.get_condition(self.content, context=context)
        text = ''
        for _i in arr:
            locals()[iter] = _i
            text += self.render_body(self.child_nodes, context=context)
        return text


# disabled
class WhileNode(Node):
    def get_condition(self, content, **kwargs):
        kwargs['tag'] = 'while'
        return super(WhileNode, self).get_condition(content, **kwargs)

    def render(self, **kwargs):
        # build node
        if kwargs.get('context'):
            context = kwargs['context']
        else:
            context = {}
        self.content = self.src_str[2:-2]
        # render
        text = ''
        while self.get_condition(self.content, context=context):
            text += self.render_body(self.child_nodes, context=context)


class IncludeNode(Node):
    def __init__(self, **kwargs):
        super(IncludeNode, self).__init__(**kwargs)
        self.filename = ''

    def build(self):
        content = self.src_str[2:-2]
        i = 0
        while i < len(content):
            if content[i:i+7] == 'include':
                i += 7
                self.filename = content[i:].split()[0]
                return
            i += 1

    def render(self, **kwargs):
        return self.content


class BlockNode(RootNode):
    def __init__(self, **kwargs):
        super(BlockNode, self).__init__(**kwargs)
        self.block_name = ''
        self.override = False

    def build(self):
        content = self.src_str[2:-2]
        i = 0
        while i < len(content):
            if content[i:i + 5] == 'block':
                i += 5
                self.block_name = content[i:].split()[0]
                return
            i += 1


class ExtendsNode(Node):
    def __init__(self, **kwargs):
        super(ExtendsNode, self).__init__(**kwargs)
        self.filename = ''

    def build(self):
        content = self.src_str[2:-2]
        i = 0
        while i < len(content):
            if content[i:i + 7] == 'extends':
                i += 7
                self.filename = content[i:].split()[0]
                return
            i += 1

    def render(self, **kwargs):
        return self.content